package com.linjiahao.oss.util;

import com.alibaba.fastjson.JSON;
import com.linjiahao.oss.config.StateSecretSecurityConfig;
import com.linjiahao.oss.vo.HmacVO;
import com.linjiahao.oss.vo.SecurityParaVO;
import com.linjiahao.oss.vo.SignParaVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.*;

/**
 * 国密工具类
 * 处理加解密、签名、验签等逻辑
 * @author suruihuang
 */
@Component
public class StateSecretSecurityUtil {
    private final static transient Logger logger = LoggerFactory.getLogger(com.linjiahao.oss.util.StateSecretSecurityUtil.class);

    @Autowired
    private StateSecretUtil stateSecretUtil;
    /**
     * 判断数据类型
     * @param object 对象
     * @return true:简单类型,false:复杂类型
     */
    public boolean isBaseType(Object object) {
        return object instanceof Integer || object instanceof Byte || object instanceof Long || object instanceof Double || object instanceof Float || object instanceof Character || object instanceof Short || object instanceof Boolean || object instanceof String || object instanceof BigDecimal || object instanceof Map || object instanceof Collection || object instanceof Object[] || object instanceof Iterator || object instanceof Date || object instanceof Array || object.getClass().isEnum();
    }

    /**
     * 对象数组转换为List发生异常(用于转换为JSON)
     * @param args
     * @return
     */
    public List<Object> toListJSON(Object args[]){
        List<Object> argList = new ArrayList<Object>();
        try{
            if(args!=null){
                for (Object arg : args) {
                    // request/response无法使用toJSON
                    if (arg instanceof HttpServletRequest) {
                        argList.add("request");
                    } else if (arg instanceof HttpServletResponse) {
                        argList.add("response");
                    }else if (arg instanceof String){
                        argList.add(arg);
                    } else {
                        argList.add(JSON.toJSON(arg));
                    }
                }
            }
        }catch (Throwable e){
            logger.error("对象数组转换为List发生异常",e);
        }
        return argList;
    }
    /**
     * 获取需要处理的参数列表
     * @param paraTypeEnum 参数类型
     * @param objData 数据对象
     * @param paraList 需要处理的参数列表
     */
    public void getSecurityPara(SecurityParaTypeEnum paraTypeEnum, Object objData, List<SecurityParaVO> paraList, Map<Object,String> existParaMap) {
        //判断数据类型
        if(objData==null){
            return;
        }
        if (objData instanceof Map) {
            Map<Object,Object> map=(Map)objData;
            Object obj=null;
            for (Map.Entry<Object, Object> entry : map.entrySet()) {
                obj=entry.getValue();
                getSecurityPara(paraTypeEnum,obj, paraList,existParaMap);
            }
        } else if (objData instanceof Collection){
            Collection list = ((Collection)objData);
            if(list!=null){
                for(Object obj:list){
                    getSecurityPara(paraTypeEnum,obj, paraList,existParaMap);
                }
            }
        }else if(objData instanceof Object[]){
            Object[] list=(Object[])objData;
            if(list!=null){
                for(Object obj:list){
                    getSecurityPara(paraTypeEnum,obj, paraList,existParaMap);
                }
            }
        }

        if (!isBaseType(objData)) {
            if(existParaMap.containsKey(objData)){
                return;
            }else{
                existParaMap.put(objData,"");
            }
            dealEntityPara(paraTypeEnum, objData,paraList);
        }
    }

    /**
     * 处理实体请求参数
     * @param paraTypeEnum 参数类型
     * @param objData 数据对象
     * @param paraList 需要处理的参数列表
     */
    private void dealEntityPara(SecurityParaTypeEnum paraTypeEnum,Object objData, List<SecurityParaVO> paraList){
        if(SecurityParaTypeEnum.ENCRYPT.getValue()==paraTypeEnum.getValue()){
            //加密
            getEncrypt(objData, paraList);
        }else if(SecurityParaTypeEnum.DECRYPT.getValue()==paraTypeEnum.getValue()){
            //解密
            getDecrypt(objData, paraList);
        }else if(SecurityParaTypeEnum.SIGN.getValue()==paraTypeEnum.getValue()){
            //签名
            getSign(objData, paraList);
        }else if(SecurityParaTypeEnum.VERIFY_SIGN.getValue()==paraTypeEnum.getValue()){
            //验签
            getVerifySign(objData, paraList);
        }
    }

    /**
     * 获取需要加密字段
     * @param objData
     * @param paraList
     */
    private void getEncrypt(Object objData, List<SecurityParaVO> paraList){
        List<String> fieldNameList= StateSecretSecurityConfig.encryptMap.get(objData.getClass().getName());
        if(fieldNameList==null){
            //该对象没有配置加解密字段
            return;
        }
        SecurityParaVO securityParaVO=null;
        for(String fieldName:fieldNameList){
            securityParaVO=new SecurityParaVO();
            securityParaVO.setFieldName(fieldName);
            securityParaVO.setObj(objData);
            paraList.add(securityParaVO);
        }
    }

    /**
     * 获取需要解密字段
     */
    private void getDecrypt(Object objData, List<SecurityParaVO> paraList){
        //需要解密的字段，和需要加密的字段一样
        getEncrypt(objData, paraList);
    }

    /**
     * 获取需要签名的字段
     * @param objData
     * @param paraList
     */
    private void getSign(Object objData, List<SecurityParaVO> paraList){
        List<SignParaVO> signParaVOList= StateSecretSecurityConfig.signMap.get(objData.getClass().getName());
        if(signParaVOList==null){
            //该对象没有配置签名、验签字段
            return;
        }
        SecurityParaVO securityParaVO=null;
        for(SignParaVO signParaVO:signParaVOList){
            securityParaVO=new SecurityParaVO();
            securityParaVO.setFieldName(signParaVO.getSignField());
            securityParaVO.setDbField(signParaVO.getDbField());
            securityParaVO.setPkField(signParaVO.getPkField());
            securityParaVO.setObj(objData);
            securityParaVO.setAgentCode(signParaVO.getAgentCode());
            securityParaVO.setTableName(signParaVO.getTableName());
            paraList.add(securityParaVO);
        }
    }

    /**
     * 获取需要验签的字段
     * @param objData
     * @param paraList
     */
    private void getVerifySign(Object objData, List<SecurityParaVO> paraList){
        getSign(objData, paraList);
    }

    /**
     * 加密
     * @param args
     */
    public List<SecurityParaVO> dealEncrypt(Object args[]) throws Throwable{
        //需要加密的字段信息列表
        List<SecurityParaVO> paraList=new ArrayList<SecurityParaVO>();
        //用于防止一个对象被重复引用造成重复加密
        Map<Object,String> existParaMap=new HashMap<Object,String>();
        for(Object arg:args){
            getSecurityPara(SecurityParaTypeEnum.ENCRYPT,arg,paraList,existParaMap);
        }
        existParaMap.clear();

        //需要加密的字段信息列表(非空值)
        List<SecurityParaVO> paraNotNullList=new ArrayList<SecurityParaVO>(paraList.size());

        if(paraList.size()>0){
            //需要加密的值列表
            List<Object> reqList=new ArrayList<Object>();
            Object getValue=null;
            for(SecurityParaVO securityParaVO:paraList){
                //执行get方法，获取需要加密的值
                getValue=StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getFieldName());
                if(getValue!=null){
                    securityParaVO.setFieldValue(getValue);
                    paraNotNullList.add(securityParaVO);
                    reqList.add(getValue);
                }
            }

            //调用国密加密接口Util
            List<String> respList=null;
            Date startTime=new Date();
            try{
                respList= stateSecretUtil.stateSecretEncrypt(reqList);
                Date endTime=new Date();
                logger.info("\n调用国密加密Util\n日志ID:{}\n请求数据[{}]:{}\n返回数据[{}]:{}\n耗时:{}毫秒\n", Thread.currentThread().getId(), startTime, JSON.toJSON(reqList),endTime,JSON.toJSON(respList),endTime.getTime()-startTime.getTime());
            }catch (Throwable e){
                Date endTime=new Date();
                logger.error("\n调用国密加密Util异常,耗时:{}毫秒\n",endTime.getTime()-startTime.getTime());
                throw e;
            }

            //设置加密字段值为密文
            Object resp=null;
            SecurityParaVO securityParaVO=null;
            for(int i=0;i<paraNotNullList.size();i++){
                securityParaVO=paraNotNullList.get(i);
                resp=respList.get(i);

                //执行set方法,设置加密字段值为密文
                StateSecretEntityUtil.invokeSetMethod(securityParaVO.getObj(),securityParaVO.getFieldName(),resp);
            }

            reqList.clear();
            reqList=null;

            respList.clear();
            respList=null;
        }

        paraList.clear();
        paraList=null;

        return paraNotNullList;
    }

    /**
     * 解密
     * @param arg
     * @param existParaMap
     */
    public void dealDecrypt(Object arg,Map<Object,String> existParaMap) throws Throwable{
        //需要解密的字段信息列表
        List<SecurityParaVO> paraList=new ArrayList<SecurityParaVO>();
        //用于防止一个对象被重复引用造成重复解密
        getSecurityPara(SecurityParaTypeEnum.DECRYPT,arg,paraList,existParaMap);
        existParaMap.clear();

        //需要解密的字段信息列表(非空值)
        List<SecurityParaVO> paraNotNullList=new ArrayList<SecurityParaVO>(paraList.size());

        if(paraList.size()>0){
            //需要解密的值列表
            List<Object> reqList=new ArrayList<Object>();
            Object getValue=null;
            for(SecurityParaVO securityParaVO:paraList){
                getValue=StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getFieldName());
                if(getValue!=null){
                    paraNotNullList.add(securityParaVO);
                    reqList.add(getValue);
                }
            }

            //调用国密解密接口Util
            List<String> respList=null;
            Date startTime=new Date();
            try{
                respList= stateSecretUtil.stateSecretDecrypt(reqList);
                Date endTime=new Date();
                logger.info("\n调用国密解密Util\n日志ID:{}\n请求数据[{}]:{}\n返回数据[{}]:{}\n耗时:{}毫秒\n", Thread.currentThread().getId(), startTime, JSON.toJSON(reqList),endTime,JSON.toJSON(respList),endTime.getTime()-startTime.getTime());
            }catch (Throwable e){
                Date endTime=new Date();
                logger.error("\n调用国密解密Util异常,耗时:{}毫秒\n",endTime.getTime()-startTime.getTime());
                throw e;
            }


            //设置加密字段值为明文
            Object resp=null;
            SecurityParaVO securityParaVO=null;
            for(int i=0;i<paraNotNullList.size();i++){
                securityParaVO=paraNotNullList.get(i);
                resp=respList.get(i);

                //执行set方法,设置解密字段值为明文
                StateSecretEntityUtil.invokeSetMethod(securityParaVO.getObj(),securityParaVO.getFieldName(),resp);
            }

            respList.clear();
            respList=null;
            reqList.clear();
            reqList=null;
        }

        paraNotNullList.clear();
        paraNotNullList=null;
        paraList.clear();
        paraList=null;
    }

    /**
     * 签名
     * @param args
     */
    public void dealSign(Object args[]) throws Throwable{
        if(args==null){
            return;
        }
        //需要签名的字段信息列表
        List<SecurityParaVO> paraList=new ArrayList<SecurityParaVO>();
        //用于防止一个对象被重复引用造成重复签名
        Map<Object,String> existParaMap=new HashMap<Object,String>();
        for(Object arg:args){
            getSecurityPara(SecurityParaTypeEnum.SIGN,arg,paraList,existParaMap);
        }
        existParaMap.clear();

        if(paraList.size()>0){
            //需要签名的值列表
            List<HmacVO> hmacVOList=new ArrayList<HmacVO>(paraList.size());
            HmacVO hmacVO=null;
            for(SecurityParaVO securityParaVO:paraList){
                //执行get方法，获取需要签名的值,ID值
                hmacVO=new HmacVO();
                hmacVO.setAgentCode(securityParaVO.getAgentCode());
                hmacVO.setField(securityParaVO.getDbField());
                Object obj = StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getFieldName());
                hmacVO.setFieldValue(obj == null ? "" : obj.toString());
                Object id = StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getPkField());
                hmacVO.setIdValue(id == null ? "" : id.toString());
                hmacVO.setTableName(securityParaVO.getTableName());
                hmacVOList.add(hmacVO);
            }

            //调用国密签名Util
            Date startTime=new Date();
            try{
                stateSecretUtil.stateSecretSign(hmacVOList);
                Date endTime=new Date();
                logger.info("\n调用国密签名Util\n日志ID:{}\n请求数据[{}]:{}\n返回数据[{}]:{}\n耗时:{}毫秒\n", Thread.currentThread().getId(), startTime, JSON.toJSON(hmacVOList),endTime,"",endTime.getTime()-startTime.getTime());
            }catch (Throwable e){
                Date endTime=new Date();
                logger.error("\n调用国密签名Util异常,耗时:{}毫秒\n",endTime.getTime()-startTime.getTime());
                throw e;
            }

            hmacVOList.clear();
            hmacVOList=null;
        }

        paraList.clear();
        paraList=null;
    }

    /**
     * 验证签名
     * @param arg 验签对象
     */
    public void dealVerifySign(Object arg) throws Throwable{
        //需要验证签名的字段信息列表
        List<SecurityParaVO> paraList=new ArrayList<SecurityParaVO>();
        //用于防止一个对象被重复引用造成重复验签
        Map<Object,String> existParaMap=new HashMap<Object,String>();
        getSecurityPara(SecurityParaTypeEnum.VERIFY_SIGN,arg,paraList,existParaMap);
        existParaMap.clear();

        if(paraList.size()>0){
            //需要签名的值列表
            List<HmacVO> hmacVOList=new ArrayList<HmacVO>(paraList.size());
            HmacVO hmacVO=null;
            for(SecurityParaVO securityParaVO:paraList){
                //执行get方法，获取需要签名的值,ID值
                hmacVO=new HmacVO();
                hmacVO.setAgentCode(securityParaVO.getAgentCode());
                hmacVO.setField(securityParaVO.getDbField());
                Object obj = StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getFieldName());
                hmacVO.setFieldValue(obj == null ? "" : obj.toString());
                Object id = StateSecretEntityUtil.invokeGetMethod(securityParaVO.getObj(),securityParaVO.getPkField());
                hmacVO.setIdValue(id == null ? "" : id.toString());
                hmacVO.setTableName(securityParaVO.getTableName());
                hmacVOList.add(hmacVO);
            }

            //调用国密验签Util
            Date startTime=new Date();
            try{
                stateSecretUtil.stateSecretHmac(hmacVOList);
                Date endTime=new Date();
                logger.info("\n调用国密验签Util\n日志ID:{}\n请求数据[{}]:{}\n返回数据[{}]:{}\n耗时:{}毫秒\n", Thread.currentThread().getId(), startTime, JSON.toJSON(hmacVOList),endTime,"",endTime.getTime()-startTime.getTime());
            }catch (Throwable e){
                Date endTime=new Date();
                logger.error("\n调用国密验签Util异常,耗时:{}毫秒\n",endTime.getTime()-startTime.getTime());
                throw e;
            }

            hmacVOList.clear();
            hmacVOList=null;
        }

        paraList.clear();
        paraList=null;
    }

    /**
     * 重置加密字段值为未加密值(调用DAO层方法后，避免原对象值被加密)
     * @param paraVOList
     */
    public void resetEncrypt(List<SecurityParaVO> paraVOList){
        if(paraVOList!=null){
            for(SecurityParaVO securityParaVO:paraVOList){
                //执行set方法,设置加密字段值为原文
                StateSecretEntityUtil.invokeSetMethod(securityParaVO.getObj(),securityParaVO.getFieldName(),securityParaVO.getFieldValue());
            }
        }
    }
}
